package com.feeyo.lib_baidutts.voice

import android.content.Context
import android.util.Log
import com.baidu.tts.client.SpeechError
import com.baidu.tts.client.SpeechSynthesizeBag
import com.baidu.tts.client.SpeechSynthesizer
import com.baidu.tts.client.SpeechSynthesizerListener
import com.baidu.tts.client.TtsMode
import com.feeyo.lib_baidutts.BuildConfig
import com.feeyo.lib_baidutts.system.ChineseToSpeech
import com.feeyo.lib_baidutts.system.PlayCallback
import java.io.DataInputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream


/**
 * 合并系统原生文本播报 百度播报 本地音频播放，可以直接依赖这个library
 */

class SpeechManager private constructor() {
    private val TAG = javaClass.simpleName
    private lateinit var mContext: Context
    private lateinit var speechRecognizer: SpeechSynthesizer
    private val PATH = "voice/"
    private var firstBaiDu = false//是否先采用百度播报
    private var currentText = ""

    //原生播报
    private lateinit var chineseToSpeech: ChineseToSpeech
    private var dirPath: String? = null
    private val needPlayWhenError = arrayListOf(
        -1, -2, -3, -4, -5, -6, -7, -8, -9, -10,
        -100, -101, -102, -103, -104, -105, -106, -107, -108, -109,
        -110, -111, -112, -113, -114, -115, -116, -117,
        -200, -201, -202, -203, -204,
        -300, -301, -302,
        -400, -401, -402, -403, -404, -405, -406
    )//语音合成出现错误的code

    //private val fileOutputStream = FileOutputStream(File(FileCacheUtil.getExternalCacheDir(context), "fail_opt_task.pcm"),true)

    private val speechSynthesizerListener = object : SpeechSynthesizerListener {
        override fun onError(utteranceId: String, error: SpeechError) {
            // 监听到出错，在此添加相关操作
            Log.e(
                "SpeechManager",
                "Speech " + "onError error=" + "(" + error.code + ")" + error.description + "--utteranceId=" + utteranceId
            )
            //code:102[离线授权下载License失败] 111[离线合成返回值非0]
            //ToastUtils.showLong("${error.code}")
            if (firstBaiDu) {
                //采原生播报
                if (ChineseToSpeech.isSupportSpeech) {
                    chineseToSpeech.speech(currentText, object : PlayCallback {
                        override fun success(data: Any) {
                            chineseToSpeech.speech(currentText, object : PlayCallback {
                                override fun success(data: Any) {
                                    Log.e(TAG, "原生朗读失败,启用本地资源")
                                    playDefaultVoice(utteranceId)
                                }
                            })
                        }
                    })
                } else {
                    Log.e(TAG, "不支持原生播报,启用本地资源")
                    playDefaultVoice(utteranceId)
                }

            } else {//本地播报失败，百度播报失败
                if (error.code in needPlayWhenError) {
                    Log.e(TAG, "百度播报失败，启用本地资源")
                    playDefaultVoice(utteranceId)
                }
            }


        }

        override fun onSpeechFinish(arg0: String) {
            // 监听到播放结束，在此添加相关操作
        }

        override fun onSpeechProgressChanged(arg0: String, arg1: Int) {
            // 监听到播放进度有变化，在此添加相关操作
        }

        override fun onSpeechStart(arg0: String) {
            // 监听到合成并播放开始，在此添加相关操作
        }

        override fun onSynthesizeDataArrived(
            utteranceId: String?,
            bytes: ByteArray?,
            progress: Int,
            engineType: Int
        ) {
            onSynthesizeDataArrived(utteranceId, bytes, progress)
        }

        fun onSynthesizeDataArrived(
            utteranceId: String?, bytes: ByteArray?, progress: Int
        ) {
            // 监听到有合成数据到达，在此添加相关操作

//            try {
//                fileOutputStream.write(bytes)
//            } catch (e: IOException) {
//                e.printStackTrace()
//            }
        }

        override fun onSynthesizeFinish(arg0: String) {
            // 监听到合成结束，在此添加相关操作
//            fileOutputStream.flush()
//            fileOutputStream.close()
        }

        override fun onSynthesizeStart(arg0: String) {
            // 监听到合成开始，在此添加相关操作
        }
    }

    companion object {
        @Volatile
        private var instance: SpeechManager? = null
        fun getInstance(): SpeechManager {
            if (instance == null) {
                synchronized(SpeechManager::class.java) {
                    if (instance == null) {
                        instance = SpeechManager()
                    }
                }
            }
            return instance!!
        }

        var userLocalSystem = false//是否优先使用系统的播报
    }

    /**
     * 如果不写配置apiKey，secretKey，appId，则使用默认的统一配置
     *
     * @param apiKey 百度apiKey
     * @param secretKey 百度secretKey
     * @param appId 百度appId
     * **/
    fun init(
        context: Context,
        apiKey: String? = null,
        secretKey: String? = null,
        appId: String? = null
    ) {
        mContext = context
        initEnvironment()
        initBaiduTTS(apiKey, secretKey, appId)
    }

    private fun playDefaultVoice(type: String) {
        Log.e(TAG, "本地资源类型：$type")
        VoiceResourceType.getName(type)?.apply {
            val pcmPlayer = PcmPlayer()
            val inputStream = DataInputStream(mContext.resources.assets.open("$PATH$this"))
            pcmPlayer.play(inputStream)
        } ?: Log.e(TAG, "没有对应类型的本地资源")
    }

    /**直接使用百度播报**/
    fun speakBaidu(text: String, type: String) {
        Log.e(TAG, "启用百度播报")
        speechRecognizer.speak(text, type)//这里还可以加参数utteranceId，配置成本地音频key，兼容已有的录音
    }


    //只需改这里即可
    fun speakAuto(text: String, type: String = "undefined") =
        if (userLocalSystem) speakAutoSystem(text, type) else speakAutoBaidu(text, type)

    /**根据系统支持情况自动选择播报方式
     *
     * 使用顺序：系统自带->百度->本地
     * @param text 播放文本
     * @param type 本地内容[ 提前录制的pcm文件 ]
     * **/
    private fun speakAutoSystem(text: String, type: String = "undefined") {
        if (text.isNotEmpty()) {
            firstBaiDu = false
            currentText = ""
            when {
                ChineseToSpeech.isSupportSpeech -> {//支持原生文本朗读
                    Log.e(TAG, "支持原生朗读，先启用原生播报")
                    chineseToSpeech.speech(text, object : PlayCallback {
                        override fun success(data: Any) {
                            chineseToSpeech.speech(text, object : PlayCallback {
                                override fun success(data: Any) {
                                    Log.e(TAG, "原生朗读失败,启用百度播报")
                                    Log.e(TAG, text)
                                    //这里还可以加参数utteranceId，配置成本地音频key，兼容已有的录音
                                    speechRecognizer.speak(text, type)
                                }
                            })
                        }
                    })
                }

                else -> {//启用百度播报
                    Log.e(TAG, "启用百度播报")
                    Log.e(TAG, text)
                    //这里还可以加参数utteranceId，配置成本地音频key，兼容已有的录音
                    speechRecognizer.speak(text, type)

                }
            }
        }
    }

    /**根据系统支持情况自动选择播报方式
     *
     * 使用顺序：百度->系统自带->本地
     * @param text 播放文本
     * @param type 本地内容[ 提前录制的pcm文件 ]
     * **/
    private fun speakAutoBaidu(text: String, type: String = "undefined") {
        if (text.isNotEmpty()) {
            currentText = text
            firstBaiDu = true
            //启用百度播报
            Log.e(TAG, "先启用百度播报")
            Log.e(TAG, text)
            //这里还可以加参数utteranceId，配置成本地音频key，兼容已有的录音
            speechRecognizer.speak(needToRepair(text, type), type)
        }
    }

    //需要修正的播报内容，后续可以删除这部分检测
    private fun needToRepair(text: String, type: String): String = when (type) {
        "taskResourceRemove" -> if (text.contains("已取消")) text else "${text}已取消"
        else -> text
    }

    fun batchSpeak(speechTextList: List<String>) {

        if (userLocalSystem) {
            Log.e(TAG, "列表启用系统播报")
            speechTextList.forEach {
                speakAutoSystem(it)
            }
        } else {
            Log.e(TAG, "列表启用百度播报")
            //批量合成并播放文本文件，一次可传入一个list，这个list会按顺序合成播放
            val bags = ArrayList<SpeechSynthesizeBag>()
            for (i in speechTextList.indices) {
                bags.add(getSpeechSynthesizeBag(speechTextList[i], i.toString()))
            }
            this.speechRecognizer.batchSpeak(bags)
        }

    }

    fun pause() {
        //暂停当前的TTS任务
        this.speechRecognizer.pause()
    }

    fun resume() {
        //恢复暂停的TTS任务
        this.speechRecognizer.resume()
    }

    fun stop() {
        //停止所有的TTS任务
        this.speechRecognizer.stop()
    }

    fun release() {
        //释放TTS的资源
        this.speechRecognizer.release()
    }

    private fun initEnvironment() {
        if (dirPath.isNullOrBlank()) {
            val sdcardPath = mContext.getExternalFilesDir("")!!.absolutePath
            dirPath = "$sdcardPath/$DIR_NAME"
        }
        makeDir(dirPath)
        copyFromAssetsToSdcard(mContext, false, TEXT_MODEL_NAME, "$dirPath/$TEXT_MODEL_NAME")
        copyFromAssetsToSdcard(
            mContext, false, SPEECH_MALE_MODEL_NAME,
            "$dirPath/$SPEECH_MALE_MODEL_NAME"
        )
        copyFromAssetsToSdcard(
            mContext, false, SPEECH_FEMALE_MODEL_NAME,
            "$dirPath/$SPEECH_FEMALE_MODEL_NAME"
        )
    }

    private fun initBaiduTTS(apiKey: String?, secretKey: String?, appId: String?) {
        this.chineseToSpeech = ChineseToSpeech(mContext)
        this.speechRecognizer = SpeechSynthesizer.getInstance()
        this.speechRecognizer.setContext(mContext)
        this.speechRecognizer.setSpeechSynthesizerListener(speechSynthesizerListener)
        // 文本模型文件路径 (离线引擎使用)
        this.speechRecognizer.setParam(
            SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE,
            "$dirPath/$TEXT_MODEL_NAME"
        )
        // 声学模型文件路径 (离线引擎使用)
        this.speechRecognizer.setParam(
            SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE,
            "$dirPath/$SPEECH_FEMALE_MODEL_NAME"
        )
        this.speechRecognizer.setParam(
            SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE,
            "$dirPath/$SPEECH_MALE_MODEL_NAME"
        )
        //        // 本地授权文件路径,如未设置将使用默认路径.设置临时授权文件路径，LICENCE_FILE_NAME请替换成临时授权文件的实际路径，仅在使用临时license文件时需要进行设置，如果在[应用管理]中开通了正式离线授权，不需要设置该参数，建议将该行代码删除（离线引擎）
        //        // 如果合成结果出现临时授权文件将要到期的提示，说明使用了临时授权文件，请删除临时授权即可。
        //        this.speechRecognizer.setParam(SpeechSynthesizer.PARAM_TTS_LICENCE_FILE, mSampleDirPath + "/" + LICENSE_FILE_NAME);
        // 请替换为语音开发者平台上注册应用得到的App ID (离线授权)/*这里只是为了让Demo运行使用的APPID,请替换成自己的id。*/
        this.speechRecognizer.setAppId(appId ?: BuildConfig.BD_VOICE_APPID)
        // 请替换为语音开发者平台注册应用得到的apikey和secretkey (在线授权)/*这里只是为了让Demo正常运行使用APIKey,请替换成自己的APIKey*/
        this.speechRecognizer.setApiKey(
            apiKey ?: BuildConfig.BD_VOICE_API_KEY,
            secretKey ?: BuildConfig.BD_VOICE_SECRET_KEY
        )
        // 发音人（在线引擎），可用参数为0,1,2,3。。。（服务器端会动态增加，各值含义参考文档，以文档说明为准。0--普通女声，1--普通男声，2--特别男声，3--情感男声。。。）
        this.speechRecognizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0")
        // 音量 默认5  （0-9）
        this.speechRecognizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "9")
        // 设置Mix模式的合成策略
        this.speechRecognizer.setParam(
            SpeechSynthesizer.PARAM_MIX_MODE,
            SpeechSynthesizer.MIX_MODE_HIGH_SPEED_SYNTHESIZE
        )
        //        // 授权检测接口(只是通过AuthInfo进行检验授权是否成功。)
        //        // AuthInfo接口用于测试开发者是否成功申请了在线或者离线授权，如果测试授权成功了，可以删除AuthInfo部分的代码（该接口首次验证时比较耗时），不会影响正常使用（合成使用时SDK内部会自动验证授权）
        //        AuthInfo authInfo = this.speechRecognizer.auth(TtsMode.MIX);
        //        if (authInfo.isSuccess()) {
        //            ToastUtil.show(context, "auth success");
        ////            toPrint("auth success");
        //        } else {
        //            String errorMsg = authInfo.getTtsError().getDetailMessage();
        //            ToastUtil.show(context, "auth failed errorMsg=" + errorMsg);
        ////            toPrint("auth failed errorMsg=" + errorMsg);
        //        }MIX_MODE
        // 初始化tts,离在线融合，在线优先
        speechRecognizer.initTts(TtsMode.ONLINE)
        // 加载离线英文资源（提供离线英文合成功能）
        //        int result = speechRecognizer.loadEnglishModel(dirPath + "/" + AppConfig.BaiduTtsConstant.ENGLISH_TEXT_MODEL_NAME, dirPath + "/" + AppConfig.BaiduTtsConstant.ENGLISH_SPEECH_FEMALE_MODEL_NAME);
        //        toPrint("loadEnglishModel result=" + result);
        //打印引擎信息和model基本信息
        //        printEngineInfo();
    }

    private fun getSpeechSynthesizeBag(text: String, utteranceId: String): SpeechSynthesizeBag {
        val speechSynthesizeBag = SpeechSynthesizeBag()
        //需要合成的文本text的长度不能超过1024个GBK字节。
        speechSynthesizeBag.text = text
        speechSynthesizeBag.utteranceId = utteranceId
        return speechSynthesizeBag
    }

    private fun makeDir(dirPath: String?) {
        if (!dirPath.isNullOrBlank()) {
            val file = File(dirPath)
            if (!file.exists()) {
                file.mkdirs()
            }
        }
    }

    /**
     * 将sample工程需要的资源文件拷贝到SD卡中使用（授权文件为临时授权文件，请注册正式授权）
     *
     * @param isCover 是否覆盖已存在的目标文件
     * @param source
     * @param dest
     */
    private fun copyFromAssetsToSdcard(
        context: Context, isCover: Boolean, source: String,
        dest: String
    ) {
        val file = File(dest)
        if (isCover || !isCover && !file.exists()) {
            var inputStream: InputStream? = null
            var fileOutputStream: FileOutputStream? = null
            try {
                inputStream = context.resources.assets.open(source)
                fileOutputStream = FileOutputStream(dest)
                inputStream.let {
                    val buffer = ByteArray(1024)
                    var size = 0
                    while (size >= 0) {
                        fileOutputStream.write(buffer, 0, size)
                        size = it.read(buffer, 0, 1024)
                    }
                }
            } catch (e: FileNotFoundException) {
                e.printStackTrace()
            } catch (e: IOException) {
                e.printStackTrace()
            } finally {
                try {
                    fileOutputStream?.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
                try {
                    inputStream?.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
    }
}
